/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

% additional_deprecated_opcodes = ["JNSTRICTEQUNDEFINED",
%                                 "JSTRICTEQUNDEFINED",
%                                 "JNEUNDEFINED",
%                                 "JEQUNDEFINED",
%                                 "JNSTRICTEQNULL",
%                                 "JSTRICTEQNULL",
%                                 "JNENULL",
%                                 "JEQNULL",
%                                 "JNSTRICTEQZ",
%                                 "JSTRICTEQZ",
%                                 "STTOGLOBALRECORD",
%                                 "STCONSTTOGLOBALRECORD",
%                                 "CREATEREGEXPWITHLITERAL",
%                                 "CLOSEITERATOR"]

% replaced_opcodes = ["JNSTRICTEQ",
%                    "JSTRICTEQ",
%                    "JNE",
%                    "JEQ"]

% skipped_opcodes = additional_deprecated_opcodes + replaced_opcodes

/* static */
constexpr bool BytecodeInst::HasId(Format format, size_t idx) {
    switch (format) {
% formats = Set.new
% Panda::instructions.each do |i|
%   n = i.operands.count(&:id?)
%   next if n == 0
%   next if ((i.opcode.upcase.include? "DEPRECATED_") || (skipped_opcodes.include? i.mnemonic.upcase))
%   fmt = i.format
%   next if formats.include? fmt
%   formats.add(fmt)
    case Format::<%= fmt.pretty.upcase %>:
        return idx < <%= n %>;  // NOLINT(readability-magic-numbers)
% end
    default:
        return false;
    }

    UNREACHABLE_CONSTEXPR();
}

/* static */
constexpr bool BytecodeInst::HasVReg(Format format, size_t idx) {
    switch (format) {
% formats = Set.new
% Panda::instructions.each do |i|
%   n = i.operands.count(&:reg?)
%   next if n == 0
%   next if ((i.opcode.upcase.include? "DEPRECATED_") || (skipped_opcodes.include? i.mnemonic.upcase))
%   fmt = i.format
%   next if formats.include? fmt
%   formats.add(fmt)
    case Format::<%= fmt.pretty.upcase %>:
        return idx < <%= n %>;  // NOLINT(readability-magic-numbers)
% end
    default:
        return false;
    }

    UNREACHABLE_CONSTEXPR();
}

/* static */
constexpr bool BytecodeInst::HasImm(Format format, size_t idx) {
    switch (format) {
% formats = Set.new
% Panda::instructions.each do |i|
%   n = i.operands.count(&:imm?)
%   next if n == 0
%   next if ((i.opcode.upcase.include? "DEPRECATED_") || (skipped_opcodes.include? i.mnemonic.upcase))
%   fmt = i.format
%   next if formats.include? fmt
%   formats.add(fmt)
    case Format::<%= fmt.pretty.upcase %>:
        return idx < <%= n %>;  // NOLINT(readability-magic-numbers)
% end
    default:
        return false;
    }

    UNREACHABLE_CONSTEXPR();
}

/* static */
constexpr size_t BytecodeInst::Size(Format format) {  // NOLINT(readability-function-size)
    switch (format) {
% formats = Set.new
% Panda::instructions.each do |i|
%   next if ((i.opcode.upcase.include? "DEPRECATED_") || (skipped_opcodes.include? i.mnemonic.upcase))
%   fmt = i.format
%   next if formats.include? fmt
%   formats.add(fmt)
    case Format::<%= fmt.pretty.upcase %>:
        return <%= fmt.size %>; // NOLINT(readability-magic-numbers)
% end
    default:
        break;
    }

    UNREACHABLE_CONSTEXPR();
}

inline BytecodeId BytecodeInst::GetId(size_t idx /* = 0 */) const {
    Format format = GetFormat();
    ASSERT_PRINT(HasId(format, idx), "Instruction doesn't have id operand with such index");

    if (!HasId(format, idx)) {
        return {};
    }

    uint32_t id = 0;
    switch (format) {
% formats = Set.new
% Panda::instructions.each do |i|
%   n = i.operands.count(&:id?)
%   next if n == 0
%   next if ((i.opcode.upcase.include? "DEPRECATED_") || (skipped_opcodes.include? i.mnemonic.upcase))
%   fmt = i.format
%   next if formats.include? fmt
%   formats.add(fmt)
%
%   id_ops = i.operands.select(&:id?)
%   offsets = id_ops.map(&:offset)
%   widths = id_ops.map(&:width)
    case Format::<%= fmt.pretty.upcase %>: {
        constexpr std::array<size_t, <%= n %>> OFFSETS{<%= offsets.join(", ") %>};
        constexpr std::array<size_t, <%= n %>> WIDTHS{<%= widths.join(", ") %>};
        id = static_cast<uint32_t>(Read64(OFFSETS[idx], WIDTHS[idx]));
        break; }
% end
    default:
        UNREACHABLE();
    }
    return BytecodeId(id);
}

ALWAYS_INLINE inline uint16_t BytecodeInst::GetVReg(size_t idx /* = 0 */) const {  // NOLINT(readability-function-size)
    Format format = GetFormat();
    ASSERT_PRINT(HasVReg(format, idx), "Instruction doesn't have vreg operand with such index");

    if (!HasVReg(format, idx)) {
        return 0;
    }

    switch (format) {
% formats = Set.new
% Panda::instructions.each do |i|
%   n = i.operands.count(&:reg?)
%   next if n == 0
%   next if ((i.opcode.upcase.include? "DEPRECATED_") || (skipped_opcodes.include? i.mnemonic.upcase))
%   fmt = i.format
%   next if formats.include? fmt
%   formats.add(fmt)
%
%   reg_ops = i.operands.select(&:reg?)
%   offsets = reg_ops.map(&:offset)
%   widths = reg_ops.map(&:width)
%
    case Format::<%= fmt.pretty.upcase %>: {
        constexpr std::array<size_t, <%= n %>> OFFSETS{<%= offsets.join(", ") %>};
        constexpr std::array<size_t, <%= n %>> WIDTHS{<%= widths.join(", ") %>};
        if (idx > <%= n - 1 %>) {
            break;
        }
        return static_cast<uint16_t>(Read64(OFFSETS[idx], WIDTHS[idx])); }
% end
    default:
        break;
    }
    UNREACHABLE();
}

template <BytecodeInst::Format FORMAT, size_t IDX /* = 0 */, bool IS_SIGNED /* = false */>
inline auto BytecodeInst::GetImm() const {  // NOLINT(readability-function-size)
    static_assert(HasImm(FORMAT, IDX), "Instruction doesn't have imm operand with such index");

% formats = Set.new
% Panda::instructions.each do |i|
%   n = i.operands.count(&:imm?)
%   next if n == 0
%   next if ((i.opcode.upcase.include? "DEPRECATED_") || (skipped_opcodes.include? i.mnemonic.upcase))
%   fmt = i.format
%   next if formats.include? fmt
%   formats.add(fmt)
%
%   imm_ops = i.operands.select(&:imm?)
%   offsets = imm_ops.map(&:offset)
%   widths = imm_ops.map(&:width)
%
    // Disable check due to clang-tidy bug https://bugs.llvm.org/show_bug.cgi?id=32203
    // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
    if constexpr (FORMAT == Format::<%= fmt.pretty.upcase %>) {
        constexpr std::array<size_t, <%= n %>> OFFSETS{<%= offsets.join(", ") %>};
        constexpr std::array<size_t, <%= n %>> WIDTHS{<%= widths.join(", ") %>};
        return Read<OFFSETS[IDX], WIDTHS[IDX], IS_SIGNED>();
    }

% end
    UNREACHABLE();
}

inline BytecodeInst::Opcode BytecodeInst::GetOpcode() const {
    uint8_t primary = GetPrimaryOpcode();
    if (primary >= <%= Panda::prefixes.map(&:opcode_idx).min %>) {  // NOLINT(readability-magic-numbers)
        uint8_t secondary = GetSecondaryOpcode();
        return static_cast<BytecodeInst::Opcode>((secondary << 8U) | primary);  // NOLINT(hicpp-signed-bitwise)
    }
    return static_cast<BytecodeInst::Opcode>(primary);
}

inline uint8_t BytecodeInst::GetSecondaryOpcode() const {
    ASSERT(GetPrimaryOpcode() >= <%= Panda::prefixes.map(&:opcode_idx).min %>);  // NOLINT(readability-magic-numbers)
    return ReadByte(1);
}

inline BytecodeInst::Format BytecodeInst::GetFormat() const {  // NOLINT(readability-function-size)
    return GetFormat(GetOpcode());
}

/* static */
constexpr BytecodeInst::Format BytecodeInst::GetFormat(Opcode opcode) {  // NOLINT(readability-function-size)
    switch(opcode) {
% Panda::instructions.each do |i|
%   if !(i.opcode.upcase.include? "DEPRECATED_") && !(skipped_opcodes.include? i.mnemonic.upcase)
    case BytecodeInst::Opcode::<%= i.opcode.upcase %>:
        return BytecodeInst::Format::<%= i.format.pretty.upcase %>;
%   end
% end
    default:
        break;
    }

    UNREACHABLE();
}

// NOLINTNEXTLINE(readability-function-size)
inline bool BytecodeInst::HasFlag(Flags flag) const {
    switch(GetOpcode()) {
% Panda::instructions.each do |i|
%   if !(i.opcode.upcase.include? "DEPRECATED_") && !(skipped_opcodes.include? i.mnemonic.upcase)
%     flag_array = i.real_properties.map {|prop| "Flags::" + prop.upcase}
%     flag_array += ['0'] if flag_array.empty?
%     flags = flag_array.join(' | ')
    case BytecodeInst::Opcode::<%= i.opcode.upcase %>:
        return ((<%= flags %>) & flag) == flag;  // NOLINT(hicpp-signed-bitwise)
%   end
% end
    default:
        return false;
    }

    UNREACHABLE();
}

// NOLINTNEXTLINE(readability-function-size)
inline bool BytecodeInst::IsThrow(Exceptions exception) const {
    switch(GetOpcode()) {
% Panda::instructions.each do |i|
%   if !(i.opcode.upcase.include? "DEPRECATED_") && !(skipped_opcodes.include? i.mnemonic.upcase)
%     exception_array = i.exceptions.map {|prop| "Exceptions::" + prop.upcase}
%     exception_array += ['0'] if exception_array.empty?
%     exceptions = exception_array.join(' | ')
    case BytecodeInst::Opcode::<%= i.opcode.upcase %>:
        return ((<%= exceptions %>) & exception) == exception;  // NOLINT(hicpp-signed-bitwise)
%   end
% end
    default:
        return false;
    }

    UNREACHABLE();
}
